using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace PhraseOrganizer
{
    class Phrase : IComparable<Phrase>
    {
        private static List<Phrase> _phrases;

        static Phrase()
        {
            InitializePhraseList();
        }

        private static void InitializePhraseList()
        {
            _phrases = new List<Phrase>();

            for (Int16 i = 1; i <= 2000; i++)
                _phrases.Add(new Phrase(i));
        }

        public static List<Phrase> Phrases
        { get { return _phrases; } }

        public static void AddPhrase(Int16 Position, string Name, byte[] Bytes)
        {
            _phrases[Position - 1] = new Phrase(Position, Name, System.IO.Path.Combine(PhrasesPath, string.Format(@"{0:0000}.SVP", Position)), Bytes);
        }

        public static void AddPhrase(Int16 Position, string Name, string Path)
        {
            _phrases[Position - 1] = new Phrase(Position, Name, Path);
        }

        public static void RenamePhrase(int Position, string Name)
        {
            _phrases[Position - 1]._name = Name;
        }

        public static void DeletePhrase(Phrase Phrase)
        {
            _phrases[Phrase.Position - 1] = new Phrase(Phrase.Position);
            _phrases[Phrase.Position - 1]._wasOriginallyFile = Phrase._wasOriginallyFile;
        }

        private static string _projectPath;

        private static string PhrasesPath
        { get { return System.IO.Path.Combine(_projectPath, string.Format(@"SEQ{0}PHS{0}", System.IO.Path.DirectorySeparatorChar)); } }

        private static string PhraseSetsPath
        { get { return System.IO.Path.Combine(_projectPath, string.Format(@"SEQ{0}RPS{0}", System.IO.Path.DirectorySeparatorChar)); } }

        public static void Load(string ProjectPath)
        {
            _projectPath = ProjectPath;

            if (Directory.Exists(PhrasesPath) && Directory.Exists(PhraseSetsPath))
            {
                string[] phraseFiles = Directory.GetFiles(PhrasesPath, "*.SVP");

                if (phraseFiles != null && phraseFiles.GetUpperBound(0) >= 0)
                {
                    Encoding encoding = Encoding.ASCII;

                    for (int i = 0; i <= phraseFiles.GetUpperBound(0); i++)
                    {
                        string phraseFileName = System.IO.Path.GetFileNameWithoutExtension(phraseFiles[i]);

                        Int16 phrasePosition;

                        if (Int16.TryParse(phraseFileName, out phrasePosition) && phrasePosition > 0 && phrasePosition <= 2000)
                        {
                            byte[] phraseFileBytes = File.ReadAllBytes(phraseFiles[i]);
                            byte[] phraseFileNameBytes = new byte[15];

                            // phrase name is from offset 0x0E - 0x1C
                            Array.Copy(phraseFileBytes, 14, phraseFileNameBytes, 0, 15);

                            string phraseName = encoding.GetString(phraseFileNameBytes);

                            Phrase.AddPhrase(phrasePosition, phraseName, phraseFiles[i]);
                        }
                    }
                }
            }
        }

        public static bool Save(string ProjectPath, out string UpdatesMade)
        {
            bool Result = false;

            UpdatesMade = "";

            StringBuilder sbUpdatesMade = new StringBuilder();

            if(ProjectPath == _projectPath && Directory.Exists(PhrasesPath) && Directory.Exists(PhraseSetsPath))
            {
                int phrasePosition;
                foreach (string file in Directory.GetFiles(PhrasesPath, "*.SVP"))
                {
                    string phraseFileName = System.IO.Path.GetFileNameWithoutExtension(file);
                    if (Int32.TryParse(phraseFileName, out phrasePosition) && phrasePosition > 0 && phrasePosition <= 2000)
                        File.Delete(file);
                }

                Encoding encoding = Encoding.ASCII;

                //List<Phrase> updateReferences = new List<Phrase>();
                //List<Phrase> removeReferences = new List<Phrase>();
                //List<Phrase> addedReferences = new List<Phrase>();

                foreach (Phrase phrase in _phrases)
                {
                    if (phrase._bytesChanged)
                    {
                        File.WriteAllBytes(phrase._path, phrase._bytes);

                        if(!phrase._wasOriginallyFile)
                            sbUpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was created.", phrase._originalPosition));
                        else
						{
							// todo: phrase was overwritten so remove all references to it in the livesets
							
							// todo: phrase was overwritten so remove all references to it in rpssets
							
                            sbUpdatesMade.AppendLine(string.Format("Phrase number {0:0000}'s bytes were updated.",
                                phrase._originalPosition));
						}
                    }
                    else if (phrase._isFile)
                    {
                        if (phrase._originalName != phrase._name)
                        {
                            byte[] nameAsBytes = encoding.GetBytes(phrase._name);
                            byte[] nameAs15Bytes = new byte[15];
                            if (nameAsBytes.GetUpperBound(0) >= 0)
                            {
                                Array.Copy(nameAsBytes, 0, nameAs15Bytes, 0, nameAsBytes.GetUpperBound(0) + 1);
                                Array.Copy(nameAs15Bytes, 0, phrase._bytes, 14, 15);

                                sbUpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was renamed from '{1}' to '{2}'",
                                    phrase._originalPosition, phrase._originalName.Trim(), phrase._name.Trim()));
                            }
                        }

                        if (phrase._originalPosition != phrase._position)
                        {
                            sbUpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was moved to {1:0000}",
                                phrase._originalPosition, phrase._position));

                            phrase._path = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(phrase._path), 
                                string.Format("{0:0000}.SVP", phrase._position));
							
							// todo: phrase was moved so update references to it in livesets
							
							// todo: phrase was moved so update references to it in rpssets
                        }

                        if (phrase._wasOriginallyFile && !phrase._isFile)
                        {
							// todo: phrase was deleted so remove all references to it in the livesets
							
							// todo: phrase was deleted so remove all references to it in rpssets
							
                            sbUpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was deleted.",
                                phrase._originalPosition));
                        }

                        File.WriteAllBytes(phrase._path, phrase._bytes);
                    }

                    //// remove references to this phrase
                    //if (phrase._wasOriginallyFile && !phrase._isFile)
                    //    removeReferences.Add(phrase);
                    
                    //// update references to this phrase
                    //if (phrase._originalPosition != phrase._position)
                    //    updateReferences.Add(phrase);

                    //if (!phrase._wasOriginallyFile && phrase._isFile)
                    //    addedReferences.Add(phrase);
                }

                //UpdatePhraseReferences(updateReferences, removeReferences, addedReferences, sbUpdatesMade);
            }

            UpdatesMade = sbUpdatesMade.ToString();

            return Result;
        }

        //private static void UpdatePhraseReferences(List<Phrase> Update, List<Phrase> Remove, List<Phrase> Added, StringBuilder UpdatesMade)
        //{
        //    if(!Directory.Exists(PhraseSetsPath))
        //        return;

        //    if (Update == null) Update = new List<Phrase>();
        //    if (Remove == null) Remove = new List<Phrase>();

        //    if (Update.Count == 0 && Remove.Count == 0)
        //        return;

        //    string[] rpsFiles = Directory.GetFiles(PhraseSetsPath, "*RPS.BIN");
        //    if (rpsFiles.GetUpperBound(0) >= 0)
        //    {
        //        foreach (string file in rpsFiles)
        //        {
        //            string phraseNumber = System.IO.Path.GetFileNameWithoutExtension(file).Substring(1, 2);

        //            FileStream strFile = File.Open(file, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);

        //            strFile.Seek(0x24, SeekOrigin.Begin);

        //            byte[] currentPhraseNumberAsBytes = new byte[2];
        //            strFile.Read(currentPhraseNumberAsBytes, 0, 2);

        //            Int16 currentPhraseNumber = BitConverter.ToInt16(currentPhraseNumberAsBytes, 0);
        //            if (currentPhraseNumber > 0)
        //            {
        //                foreach (Phrase phrase in Update)
        //                {
        //                    if (phrase._originalPosition == currentPhraseNumber)
        //                    {
        //                        strFile.Seek(-2, SeekOrigin.Current);

        //                        byte[] newPhraseNumberAsBytes = BitConverter.GetBytes(phrase._position);

        //                        strFile.Write(newPhraseNumberAsBytes, 0, 2);

        //                        UpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was found in RPS Set {1} and was updated to {2:0000}",
        //                            currentPhraseNumber, phraseNumber, phrase._position));

        //                        //Debug.WriteLine(string.Format("Update this phrase number: {0} in rps set {1} at Adress {2:X4}",
        //                        //    currentPhraseNumber, System.IO.Path.GetFileName(file), strFile.Position - 2));
        //                    }
        //                }

        //                foreach (Phrase phrase in Remove)
        //                {
        //                    if (phrase._originalPosition == currentPhraseNumber)
        //                    {
        //                        strFile.Seek(-2, SeekOrigin.Current);

        //                        strFile.WriteByte(0);
        //                        strFile.WriteByte(0);

        //                        UpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was found in RPS Set {1} and was removed.",
        //                            currentPhraseNumber, phraseNumber));

        //                        //Debug.WriteLine(string.Format("Remove this phrase number: {0} in rps set {1} at Adress {2:X4}",
        //                        //    currentPhraseNumber, System.IO.Path.GetFileName(file), strFile.Position - 2));
        //                    }
        //                }

        //                foreach (Phrase phrase in Added)
        //                {
        //                    strFile.Seek(-2, SeekOrigin.Current);

        //                    strFile.WriteByte(0);
        //                    strFile.WriteByte(0);

        //                    UpdatesMade.AppendLine(string.Format("Phrase number {0:0000} was added.",
        //                        currentPhraseNumber, phraseNumber));

        //                    //Debug.WriteLine(string.Format("Remove this phrase number: {0} in rps set {1} at Adress {2:X4}",
        //                    //    currentPhraseNumber, System.IO.Path.GetFileName(file), strFile.Position - 2));
        //                }
                        
        //                strFile.Seek(4, SeekOrigin.Current);
        //            }
        //        }
        //    }
        //}

        public static bool MoveSelection(int StartPosition, int Length, int EndPosition)//, bool Overwrite)
        {
            bool Result = false;

            if (StartPosition >= 1 && StartPosition < 2000 &&
                EndPosition >= 1 && EndPosition <= 2000 &&
               (StartPosition + Length - 1) <= 2000 &&
               (EndPosition + Length - 1) <= 2000)
            {
                if (StartPosition > EndPosition)
                {
                    //if(!Overwrite && !IsContiguousSpaceAvailable(EndPosition, Length))
                    //    return false;

                    List<Phrase> newPhrases = new List<Phrase>();

                    // first add the phrases before the destination range
                    if (EndPosition > 1)
                        for (int i = 0; i < EndPosition - 1; i++)
                            newPhrases.Add(_phrases[i]);

                    // add the source range
                    for (int i = StartPosition - 1; i < StartPosition + Length - 1; i++)
                        newPhrases.Add(_phrases[i]);

                    // add the phrases starting fom EndPosition to StartPosition
                    for (int i = EndPosition - 1; i < StartPosition - 1; i++)
                        newPhrases.Add(_phrases[i]);

                    // add the phrases after the source range
                    for (int i = StartPosition + Length - 1; i < 2000; i++)
                        newPhrases.Add(_phrases[i]);

                    for (Int16 i = 0; i < 2000; i++)
                        newPhrases[i].Position = (Int16)(i + 1);

                    _phrases = newPhrases;
                }
                else if (StartPosition < EndPosition)
                {
                    //if (!Overwrite && !IsContiguousSpaceAvailable(EndPosition, Length))
                    //    return false;

                    List<Phrase> newPhrases = new List<Phrase>();

                    // first add the phrases before the range of to be moved phrases
                    for (int i = 0; i < StartPosition - 1; i++)
                        newPhrases.Add(_phrases[i]);

                    // add the phrases after the source range but before the destination range
                    for (int i = StartPosition + Length - 1; i < EndPosition + Length - 1; i++)
                        newPhrases.Add(_phrases[i]);

                    // add the source range
                    for (int i = StartPosition - 1; i < StartPosition + Length - 1; i++)
                        newPhrases.Add(_phrases[i]);

                    // add the phrases after the destination range
                    for (int i = EndPosition + Length - 1; i < 2000; i++)
                        newPhrases.Add(_phrases[i]);

                    for (Int16 i = 0; i < 2000; i++)
                        newPhrases[i].Position = (Int16)(i + 1);

                    _phrases = newPhrases;
                }
            }

            return Result;
        }

        //private static bool IsContiguousSpaceAvailable(int StartPosition, int Length)
        //{
        //    bool Result = true;

        //    for (int i = StartPosition; i <= StartPosition + Length; i++)
        //    {
        //        if (_phrases[i - 1].IsFile)
        //        {
        //            Result = false;
        //            break;
        //        }
        //    }
        //    return Result;
        //}

        public static bool GetContiguousSelectedIndices(List<Phrase> selection, out int StartPosition, out int Length)
        {
            int EndPosition = -1;

            StartPosition = -1;
            Length = 0;

            if (selection == null)
                return false;

            bool Result = true;

            StartPosition = Int32.MaxValue;
            EndPosition = -1;
            int PreviousPosition = -1;

            foreach (Phrase phrase in selection)
            {
                if (phrase.Position < StartPosition)
                    StartPosition = phrase.Position;

                if (phrase.Position > EndPosition)
                    EndPosition = phrase.Position;

                if (PreviousPosition > -1 && PreviousPosition != phrase.Position - 1)
                {
                    Result = false;
                    break;
                }

                PreviousPosition = phrase.Position;
            }

            Length = EndPosition - StartPosition + 1;

            return Result;
        }

        public static bool Defragment(int StartPosition, int Length)
        {
            bool Result = false;

            if (StartPosition < 1)
                StartPosition = 1;

            if (StartPosition > 2000)
                return true;

            if (StartPosition + Length - 1 > 2000)
                Length = 2000 - StartPosition;

            List<Phrase> newPhrases = new List<Phrase>();

            // add all phrases before StartPosition
            if (StartPosition > 1)
                for (int i = 0; i < StartPosition - 1; i++)
                    newPhrases.Add(_phrases[i]);

            List<Phrase> emptyPhrases = new List<Phrase>();

            for (int i = StartPosition - 1; i < StartPosition + Length - 1; i++)
            {
                if (_phrases[i].IsFile)
                    newPhrases.Add(_phrases[i]);
                else
                    emptyPhrases.Add(_phrases[i]);
            }

            foreach (Phrase phrase in emptyPhrases)
                newPhrases.Add(phrase);

            for (int i = StartPosition + Length - 1; i < 2000; i++)
                newPhrases.Add(_phrases[i]);

            for (Int16 i = 0; i < 2000; i++)
                newPhrases[i].Position = (Int16)(i + 1);

            _phrases = newPhrases;

            return Result;
        }

        public static bool Alphabetize(int StartPosition, int Length)
        {
            bool Result = false;

            if (StartPosition < 1)
                StartPosition = 1;

            if (StartPosition > 2000)
                return true;

            if (StartPosition + Length - 1 > 2000)
                Length = 2000 - StartPosition;

            List<Phrase> newPhrases = new List<Phrase>();

            // add all phrases before StartPosition
            if (StartPosition > 1)
                for (int i = 0; i < StartPosition - 1; i++)
                    newPhrases.Add(_phrases[i]);

            List<Phrase> emptyPhrases = new List<Phrase>();
            List<Phrase> alphabetizeThesePhrases = new List<Phrase>();

            for (int i = StartPosition - 1; i < StartPosition + Length - 1; i++)
            {
                if (_phrases[i].IsFile)
                    alphabetizeThesePhrases.Add(_phrases[i]);
                else
                    emptyPhrases.Add(_phrases[i]);
            }

            alphabetizeThesePhrases.Sort(NameSorter);

            foreach (Phrase phrase in alphabetizeThesePhrases)
                newPhrases.Add(phrase);

            foreach (Phrase phrase in emptyPhrases)
                newPhrases.Add(phrase);

            for (int i = StartPosition + Length - 1; i < 2000; i++)
                newPhrases.Add(_phrases[i]);

            for (Int16 i = 0; i < 2000; i++)
                newPhrases[i].Position = (Int16)(i + 1);

            _phrases = newPhrases;

            return Result;
        }

        public static IComparer<Phrase> NameSorter
        { get { return new AscendingNameComparer(); } }

        private class AscendingNameComparer : IComparer<Phrase>
        {
            public int Compare(Phrase x, Phrase y)
            {
                if (x == null && y == null) return 0;
                else if (x == null) return -1;
                else if (y == null) return 1;

                if (x == y) return 0;

                return x.Name.CompareTo(y.Name);
            }

        }

        private bool _isFile;
        private bool _wasOriginallyFile;
        private bool _bytesChanged;
        private Int16 _position;
        private Int16 _originalPosition;
        private string _originalName;
        private string _name;
        private string _path;
        private byte[] _bytes;

        private Phrase(Int16 Position)
        {
            _isFile = false;
            _wasOriginallyFile = false;
            _position = Position;
            _originalPosition = Position;
            _originalName = "(---)";
            _name = _originalName;
            _path = "";
        }

        private Phrase(Int16 Position, string Name, string Path)
        {
            _isFile = true;
            _wasOriginallyFile = true;
            _position = Position;
            _originalPosition = Position;
            _name = Name;
            _originalName = Name;
            _path = Path;

            try { _bytes = File.ReadAllBytes(_path); }
            catch { }
        }

        private Phrase(Int16 Position, string Name, string Path, byte[] Bytes)
        {
            if (File.Exists(Path))
            {
                _isFile = true;
                _wasOriginallyFile = true;
            }
            else
            {
                _isFile = false;
                _wasOriginallyFile = false;
            }

            _bytesChanged = true;
            _position = Position;
            _originalPosition = Position;
            _name = Name;
            _originalName = Name;
            _path = Path;
            _bytes = Bytes;
        }

        public bool IsFile
        { get { return _isFile; } }

        public string Name
        { get { return _name; } }

        public Int16 Position
        {
            get { return _position; }
            set { _position = value; }
        }

        public int OriginalPosition
        { get { return _originalPosition; } }

        public string OriginalName
        { get { return _originalName; } }

        public string Path
        { get { return _path; } }

        public override string ToString()
        {
            bool Changed = (_name != OriginalName || _position != _originalPosition || _isFile != _wasOriginallyFile || _bytesChanged);

            return string.Format("{0:0000}: {1} {2}",
                _position, Changed ? "*":"", _name);
        }

        #region IComparable<Phrase> Members

        int IComparable<Phrase>.CompareTo(Phrase other)
        {
            return this.Position.CompareTo(other.Position);
        }

        #endregion
    }
}
